/*-
 * Copyright (c) 2007-2008 SINA Corporation, All Rights Reserved.
 *  Authors:
 *      Zhu Yan <zhuyan@staff.sina.com.cn>
 *
 * A HTTP request Implementation
 */
#include <libmemcached/memcached.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>

#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>

#include "libmod.h"
#include "log.h"
#include "pic_download.h"
#include "confparser.h"

#define HR_DEFAULT_HOST "127.0.0.1"
#define HR_DEFAULT_MC "mem.proxy.photo.sina.com.cn"
#define HTTP_REQUEST_ERROR	mod_conf_priority

int mod_init(char *);

static char	conf_hr_default_pic_etag[CONF_ITEM_LEN + 1]
	= "C073BE97-D761-FEEF-9F17-B13ED27996FF";

static char	conf_hr_deleted_pic_etag[CONF_ITEM_LEN + 1]
	= "83E476A5-DEFE-4E3C-AABB-5131EDA22478";
static char	conf_hr_mc_server[CONF_ITEM_LEN + 1] = HR_DEFAULT_MC;
static int	conf_hr_sleep = 3;


static CONF_STR_CONFIG conf_str_array[] = {
	{"default_pic_etag",	conf_hr_default_pic_etag},
	{"deleted_pic_etag",	conf_hr_deleted_pic_etag},
	{"mc_server",	conf_hr_mc_server},
	{0, 0}
};
static CONF_INT_CONFIG conf_int_array[] = {
	{"sleep", &conf_hr_sleep},
	{0, 0}
};

static int
getnstr(char *buf, const char *str, size_t n)
{
	int r = 0;

	if (n != 0) {
		char *d = buf;
		const char *s = str;

	/* 
	 * clean the space after the first charactor of '\r', '\n' or 0 in the string
	 * put the string into buffer
	 * and get the position that the string ends
	 */
		do {
			*d = *s;
			if ( *d == '\r' || *d == '\n' || *d == 0) {
				/* NUL pad the remaining n-1 bytes */
				r = d - buf;
				while (--n != 0)
					*d++ = 0;
				break;
			}
			d++;
			s++;
		} while (--n != 0);
	}

	/* return the position that the string ends.*/
	return (r);
}


static char *
findcasestr(char *s, const char *find, size_t slen)
{
		char c, sc;
		size_t len;

		if ((c = *find++) != '\0') {
				c = tolower((unsigned char)c);
				len = strlen(find);
				do {
						do {
								if (slen-- < 1 || (sc = *s++) == '\0')
										return (NULL);
						} while ((char)tolower((unsigned char)sc) != c);
						if (len > slen)
								return (NULL);
				} while (strncasecmp(s, find, len) != 0);
				s--;
		}
		return ((char *)s);
}

static size_t
get_etag( void *ptr, size_t size, size_t nmemb, void *data)
{
	PIC_DATA *pdp;
	size_t realsize = size * nmemb;
	char *pos;
	char buf[1024];

	pdp = (PIC_DATA *)data;
	if (pdp == NULL)
		return (CURLE_WRITE_ERROR);

	pos = findcasestr(ptr, "etag: ", realsize);

	if (pos != NULL) {
		getnstr(buf, pos, realsize - ((char *)pos - (char *)ptr));
		if (strncasecmp(buf + 6, conf_hr_default_pic_etag, 1024) == 0) {
			pdp->is_default = 1;
			WLOG_INFO("%s is a default picture!", pdp->pic_url);
			return (CURLE_WRITE_ERROR);
		} else if (strncasecmp(buf + 6, conf_hr_deleted_pic_etag, 1024) == 0) {
			pdp->is_deleted = 1;
			WLOG_INFO("%s is a deleted picture!", pdp->pic_url);
			return (CURLE_WRITE_ERROR);
		}
	}
	return (realsize);
}

static size_t
store_to_mem(void *ptr, size_t size, size_t nmemb, void *data)
{
	PIC_DATA *pdp;
	size_t realsize = size * nmemb;
	DATA_BUFFER_NODE *dbnp;
	DATA_BUFFER_NODE *dbnp1;
	int sub_len;
	int v_len;
	int inc = 0;
	char *buf;
	
	pdp = (PIC_DATA *)data;
	if (pdp == NULL)
		return (CURLE_WRITE_ERROR);
	/* 
	 * if the tailed queue is empty, 
	 * generate a queue and initialize a the head
	 */
	if (TAILQ_EMPTY(&(pdp->data_buffer_head))) {
		dbnp = (DATA_BUFFER_NODE *)calloc(1, sizeof(DATA_BUFFER_NODE));
		buf = (char *)calloc(1, BUFFER_BLOCK_SIZE);
		pdp->block_num = 1;
		dbnp->data_len = 0;
		dbnp->data = buf;
		TAILQ_INSERT_TAIL(&(pdp->data_buffer_head), dbnp, data_buffer_entries);
	} else {
		dbnp = TAILQ_LAST(&(pdp->data_buffer_head), data_buffer_head);
	}
	
	/* if the space is enough for the data, just copy them directly */
	if ((dbnp->data_len + realsize) <= BUFFER_BLOCK_SIZE) {
		memcpy(dbnp->data + dbnp->data_len, (unsigned char*)ptr, realsize);
		dbnp->data_len += realsize;
		inc += realsize;
	} else {
		if(pdp->block_num < MAX_BLOCK_NUMBER) {
			sub_len = BUFFER_BLOCK_SIZE - dbnp->data_len;
			memcpy(dbnp->data + dbnp->data_len, (unsigned char*)ptr, sub_len);
			dbnp->data_len = BUFFER_BLOCK_SIZE;
			inc += sub_len;

			for (v_len = realsize - inc; 
				v_len > 0 && pdp->block_num < MAX_BLOCK_NUMBER;
				v_len = realsize - inc) {

				dbnp1 = (DATA_BUFFER_NODE *)calloc(1, sizeof(DATA_BUFFER_NODE));
				if (dbnp1 == NULL)
					return (CURLE_WRITE_ERROR);
				buf = (char *)calloc(1, BUFFER_BLOCK_SIZE);
				if (buf == NULL)
					return (CURLE_WRITE_ERROR);

				pdp->block_num++;
				dbnp1->data = buf;
				dbnp1->data_len = 0;

				if (v_len <= BUFFER_BLOCK_SIZE) {
					memcpy(dbnp1->data, 
						(unsigned char*)ptr + inc, 
						v_len);
					dbnp1->data_len = v_len;
					inc += v_len;
				} else {
					memcpy(dbnp1->data, 
						(unsigned char*)ptr + inc, 
						BUFFER_BLOCK_SIZE);
					dbnp1->data_len = BUFFER_BLOCK_SIZE;
					inc += BUFFER_BLOCK_SIZE;
				}
				TAILQ_INSERT_TAIL(&(pdp->data_buffer_head), 
				dbnp1, data_buffer_entries);
			}

		} 
	}

	if (pdp->block_num > MAX_BLOCK_NUMBER ){
		/* but if the data exceed the limitation, it can't be handled */ 
		WLOG_ERR("MAX_BLOCK_NUMBER: %d, ls->block_num: %d\n", 
			MAX_BLOCK_NUMBER, pdp->block_num);
		return (CURLE_WRITE_ERROR);
	}
	return (realsize);
}

static ssize_t 
hr_init(void *arg, void **res)
{
	memcached_st *memp;
	memcached_st **mempp;
	memcached_return retval;

	CSF_UNUSED_ARG(arg);
	
	mempp = (memcached_st **)res;

	if (mempp == NULL)
		return (PIPELINE_FAILED);
	
	memp = memcached_create (NULL);	
	*mempp = memp;
	if (memp == NULL)
		return (PIPELINE_FAILED);
/*
	if (!memcached_behavior_set (memp, MEMCACHED_BEHAVIOR_NO_BLOCK, 1))
		return (PIPELINE_FAILED); 
	if (!memcached_behavior_set (memp, MEMCACHED_BEHAVIOR_TCP_NODELAY, 1))
		return (PIPELINE_FAILED); 
	if (!memcached_behavior_set (memp, MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, 3))
		return (PIPELINE_FAILED); 
*/
	retval = memcached_server_add (memp,conf_hr_mc_server,11211);
	if (retval != MEMCACHED_SUCCESS)
		return (PIPELINE_FAILED);

	return (PIPELINE_SUCCESS);
}

static void 
hr_deinit(void *arg, void *res)
{
	memcached_st *memp;
	CSF_UNUSED_ARG(arg);

	memp = (memcached_st *)res;
	if (memp == NULL) {
		WLOG_ERR("memcached_st is NULL!");
		return;
	}

	memcached_free(memp);
	return;
}


static ssize_t 
hr_handler(WTI *wtip, CONN_INFO *cip, void *data, void **res)
{
	int retval;
	CURL *curl_handle;
	char buf[1024];
	PIC_DATA *pdp;
	long resp_code = 0;
	memcached_st *memp;
	memcached_st **mempp;
	memcached_return ret;
	size_t val_len;
	time_t t;
	struct stat sb;

	CSF_UNUSED_ARG(cip);
	CSF_UNUSED_ARG(wtip);

	if (res == NULL)
		return (PIPELINE_FAILED);

	mempp = (memcached_st**)res;
	memp = *mempp;
	if (memp == NULL)
		return (PIPELINE_FAILED);

	pdp = (PIC_DATA *)data;
	if (pdp == NULL)
		return (PIPELINE_FAILED);
	
	/* sleep for service generate a pic */
	usleep(conf_hr_sleep*1000);
	
	if (0 == lstat(pdp->pic_path, &sb)) {
			WLOG_ERR("file %s exist!", pdp->pic_path);
			return (PIPELINE_BREAK);
	}

	val_len = snprintf(buf, 1023, "%d", (uint32_t)time(&t));
	
	ret = memcached_add (memp,
		pdp->pic_url, strlen(pdp->pic_url),
		buf, val_len, 60*5, 0);
	if (ret != MEMCACHED_SUCCESS) {
		WLOG_ERR("memcache_add() error: %s, pic_url: %s, buf: %s", 
			memcached_strerror(memp, ret), pdp->pic_url, buf);
		return (PIPELINE_BREAK);
	}

	/* init the curl session */
	curl_handle = curl_easy_init();

	/* specify URL to get */
	curl_easy_setopt(curl_handle, CURLOPT_URL, pdp->pic_url);
	curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);

	/* 
	 * If the source server is not service.photo.sina.com.cn,
	 * we set a redirect option for picture downloading.
	 * Because we could download the picture from the home of picture,
	 * therefore, we might NOT download the picture in the first time.
	 * The home server would send a "302 Location:xxx" to me to redirect
	 * the http request to service.photo.sina.com.cn.
	 * The service.photo.sina.com.cn is the final site for picture downloading,
	 * So if the URL is contain service.photo, we MUST NOT redirect it anymore.
	 */
	if (strstr(pdp->pic_url, "service.photo") == NULL) {
		curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
		curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 1);
	}
	curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 120);
	curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 10);
	/* send all data to this function  */
	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, store_to_mem);
	/* we pass our 'chunk' struct to the callback function */
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, pdp);
	/* send all data to this function  */
	curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, get_etag);
	/* we pass our 'chunk' struct to the callback function */
	curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, pdp);

	/* some servers don't like requests that are made without a user-agent
	field, so we provide one */
	curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "sina_pic_downloader/1.0");
	curl_easy_setopt(curl_handle, CURLOPT_REFERER, "http://blog.sina.com.cn");
	
	curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);

	/* get it! */
	retval = curl_easy_perform(curl_handle);
	if (retval != 0)
		goto error;

	curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &resp_code);
	if (resp_code != 200)
		goto error;

	/* cleanup curl stuff */
	curl_easy_cleanup(curl_handle);
	return (PIPELINE_CONTINUE);

error:

	WLOG_ERR("Download error: %d, RESPONSE_CODE: %ld, URL: %s", 
		retval, resp_code, pdp->pic_url);

	if (retval == CURLE_TOO_MANY_REDIRECTS ||
		(retval == CURLE_WRITE_ERROR && pdp->is_default) ||
		resp_code == 307) {
		/* The picture is not existed, whatever to create a symlink */
		symlink("../../default_pic.gif", pdp->pic_path);
		WLOG_INFO("create default symlink: %s", pdp->pic_path);
	} else if (retval == CURLE_WRITE_ERROR && pdp->is_deleted) {
				/* The picture is not existed, whatever to create a symlink */
		symlink("../../deleted_pic.gif", pdp->pic_path);
		WLOG_INFO("create deleted symlink: %s", pdp->pic_path);
	}

	retval = memcached_delete (memp, pdp->pic_url, strlen(pdp->pic_url), 0);
	curl_easy_cleanup(curl_handle);

	return (PIPELINE_BREAK);
}

int 
mod_init(char *mod_name)
{
	int r;

	r = load_conf(NULL, mod_name, conf_int_array, conf_str_array);
	if (r != 0)
		WLOG_ERR("load_conf() failed!");

	set_request_init(hr_init);
	set_request_deinit(hr_deinit);
	set_request_handler(hr_handler);

	return (0);
}

